module TrigonometricFunc(
  iRstN,
  iClk,
  iStartProcess,
  iFunc,          //00: Sin; 01: Cos; 10: Acos; 11: Error
  oBusy,
  oError,
  iData,
  oData
);

input iRstN, iClk, iStartProcess;
input [1:0] iFunc;
output reg oBusy = 0, oError = 0;
input[31:0] iData;
output reg[31:0] oData = 0;

// --------------- Parameters -----------------------------------
localparam PI       = 32'h40490fdb; // Pi
localparam _PI      = 32'hc0490fdb; // -Pi
localparam PI2      = 32'h40c90fdb; // 2*Pi
localparam PI_2     = 32'h3FC90FDB; // Pi/2
localparam PI3_2    = 32'h4096CBE4; // 3*Pi/2
localparam C8192_PI = 32'h4522F983; // 8192 / Pi
localparam C4096    = 32'h45800000; // 4096
localparam C1       = 32'h3f800000; // +1
localparam C_1      = 32'hbf800000; // -1


// --------------- Internal registers and wires -----------------
reg prevStartProcess = 1;
reg negative = 0;
reg[31:0] data = 0, temp = 0, addr1 = 0, delta = 0;
reg[4:0]  state = 0;
reg[5:0]  timer = 0;
reg[13:0] preAddress = 0;
reg[1:0]  func = 0;

reg[31:0]  intToFloatA = 0;
wire[31:0] intToFloatResult;

reg[31:0]  floatToIntA = 0;
wire[31:0] floatToIntResult;

reg[31:0]  floatAddA = 0, floatAddB = 0;
wire[31:0] floatAddResult;

reg[31:0]  floatMultipleA = 0, floatMultipleB = 0;
wire[31:0] floatMultipleResult;

reg[11:0]  sinTableRomAddress = 0;
wire[31:0] sinTableRomQ;

reg[12:0]  acosTableRomAddress = 0;
wire[31:0] acosTableRomQ, acosQ;

// --------------- assign blocks ----------------------------------
assign acosTableRomQ = (acosTableRomAddress == 4096) ? 0 : acosQ;

// --------------- always blocks ----------------------------------
always@(posedge iClk or negedge iRstN)
begin
  if(!iRstN) begin
    oBusy <= 1'b0;
    prevStartProcess <= 1'b1;
    state <= 5'd0;
    timer <= 6'd0;
    oError <= 1'b0;
    oData <= 0;
  end
  else begin
    prevStartProcess <= iStartProcess;  
    if({prevStartProcess,iStartProcess} == 2'b01 && !oBusy) begin
      func <= iFunc;
      data <= iData;   
      timer <= 6'd0;
      oBusy <= 1'b1;
      oError <= 0;
     
      if(iFunc[1] == 1'b0) begin
        if(iFunc[0] == 1'b0) negative <= iData[31];
        else negative <= 0;
        
        floatMultipleA <= {1'b0, iData[30:0]};
        floatMultipleB <= C8192_PI;
        state <= 5'd1;
      end
      else if(iFunc[0] == 1'b0) begin
        if(iData[30:0] > C1) state <= 5'd0; // Error
        else begin
         floatMultipleA <= {1'b0, iData[30:0]};
         floatMultipleB <= C4096;
         state <= 5'd5;
        end
      end
      else state <= 5'd0; // Error   
    end
    
    else if(oBusy) begin
      case (state)
        //--------------------------------------------
        //Error state
        0: begin
          oError <= 1'b1;
          oData <= 32'h7FFFFFFF;
         
          if(timer == 'd4) oBusy <= 1'b0;
          else timer <= timer + 1'b1;
        end
        //--------------------------------------------
        //Sin and Cos state    
        1: begin          
          if(timer == 6'd4) begin   //wait till mult finished
            timer <= 6'd0;
            state <= 5'd2;
            floatToIntA <= floatMultipleResult;
          end
          else timer <= timer + 1'b1;
        end
        2: begin          
          if(timer == 6'd5) begin   //wait till convert finished
            timer <= 6'd0;
            state <= 5'd3;
            preAddress <= (floatToIntResult + (func[0] == 1'b1 ? 'd4096 : 'd0)) % 'd16384;
          end
          else timer <= timer + 1'b1;
        end
        3: begin
          if(preAddress < 'd4096) sinTableRomAddress <= preAddress[11:0];
          else if(preAddress == 'd4096) begin
            oData <= C1;
            oBusy <= 1'b0;
          end
          else if(preAddress < 'd8192) sinTableRomAddress <= 'd8192 - preAddress[12:0];
          else begin
            negative <= ~negative;
            if(preAddress < 'd12288) sinTableRomAddress <= preAddress - 'd8192;
            else if(preAddress == 'd12288) begin
              oData <= C_1;
              oBusy <= 1'b0;
            end
            else sinTableRomAddress <= 'd16384 - preAddress;
          end
          state <= 5'd4;
        end
        4: begin         
          if(timer == 6'd1) begin  //wait till read finished
            timer <= 6'd0; 
            oData[31] <= negative;
            oData[30:0] <= sinTableRomQ[30:0]; //set the output data       
            oBusy <= 1'b0;
          end
          else timer <= timer + 1'b1;
        end
        
        //--------------------------------------------
        //ACos calculation
        //data is in the interval of -1->1      
        5: begin          
          if(timer == 'd4) begin   //wait till x4096 finished
            timer <= 'd0;
            state <= 'd6;
            floatToIntA <= floatMultipleResult; // convert to int to get LUT address
            temp <= floatMultipleResult;
          end
          else timer <= timer + 1'b1;
        end
        6: begin          
          if(timer == 'd5) begin   //wait till mem address conversion finished
            timer <= 'd0;
            state <= 'd7;
            acosTableRomAddress <= floatToIntResult[12:0]; // fetch data
            intToFloatA <= floatToIntResult[12:0];  // convert back to float to calculate error
          end
          else timer <= timer + 1'b1;
        end
        7: begin
          if(timer == 'd5) begin
            timer <= 'd0;
            floatAddA <= temp;     // input * 4096 - LUT address
            floatAddB <= {1'b1, intToFloatResult[30:0]};
            addr1 <= acosTableRomQ; // store LUT data
            state <= 'd8;
          end
          else timer <= timer + 1'b1;
        end
        8: begin
          if(timer == 'd6) begin
            timer <= 'd0;
            if(floatAddResult == 0) begin  // if no error, no interpolation needed
              temp <= addr1;
              state <= 'd13;
            end
            else begin
              delta <= floatAddResult;   // address error
              if(floatAddResult[31]) acosTableRomAddress <= acosTableRomAddress - 1'd1; // fetching neighbour data
              else acosTableRomAddress <= acosTableRomAddress + 1'd1;
              state <= 'd9;
            end
          end
          else timer <= timer + 1'b1;
        end
        9:begin
          if(timer == 'd1) begin // calculate *LUT - *(LUT+1)
            timer <= 'd0;
            floatAddA <= delta[31] ? addr1 : acosTableRomQ;
            floatAddB <= delta[31] ? {1'b1, acosTableRomQ[30:0]} : {1'b1, addr1[30:0]};
            state <= 'd10;
          end
          else timer <= timer + 1'b1;
        end    
        10: begin   
          if(timer == 'd6) begin
            timer <= 'd0;
            floatMultipleA <= delta;  // address error * difference
            floatMultipleB <= floatAddResult;
            state <= 'd11;
          end
          else timer <= timer + 1'b1;      
        end
        11: begin
          if(timer == 'd4) begin
            timer <= 'd0;
            floatAddA <= floatMultipleResult;
            floatAddB <= addr1;
            state <= 'd12;
          end
          else timer <= timer + 1'b1;
        end
        12: begin
          if(timer == 'd6) begin
            timer <= 'd0;
            temp <= floatAddResult;
            state <= 'd13;
          end
          else timer <= timer + 1'b1;
        end
        13: begin          
          if(data[31] == 1'b0) begin
            oData <= temp;
            oBusy <= 1'b0;
          end
          else begin
            floatAddA <= PI;
            floatAddB <= {1'b1, temp[30:0]};
            state <= 'd14;
          end 
        end
        14: begin
          if(timer == 6'd6) begin   //wait till add finished
            oData <= floatAddResult;
            oBusy <= 1'b0;
          end
          else timer <= timer + 1'b1; 
        end
      endcase
    end
  end
end

// --------------- internal modules --------------------------------
//6T
IntToSinglePrecFloatConvert i2fp(
  .clock(~iClk),
  .dataa(intToFloatA),
  .result(intToFloatResult)
);

//6T
SinglePrecFloatToInt u5(
  .clock(~iClk),
  .dataa(floatToIntA),
  .result(floatToIntResult)
);

//7T
SinglePrecFloatAdd u1(
  .clock(~iClk),
  .dataa(floatAddA),
  .datab(floatAddB),
  .result(floatAddResult)
);

//5T
SinglePrecFloatMult m1(
  .dataa(floatMultipleA),
  .datab(floatMultipleB),
  .result(floatMultipleResult),
  .clock(~iClk)
);

SinTableRom u6(
  .address(sinTableRomAddress),
  .clock(~iClk),
  .q(sinTableRomQ)
);

AcosTableRom u8(
  .address(acosTableRomAddress[11:0]),
  .clock(~iClk),
  .q(acosQ)
);

endmodule
